#region imported Namespaces
using System;
using System.Collections;
using System.Data;
using System.Data.SqlClient;

using BO = PHSRAG.Insight21.BusinessObjects;
using DataAccess = PHSRAG.Utility.DataAccess;
using ICachePolicy = PHSRAG.Insight21.Policy.ICachePolicy;
using DistribPeriodConstants = PHSRAG.Insight21.Personnel.Constants.DistributionPeriodConstants;
using SplitConstants = PHSRAG.Insight21.Personnel.Constants.DistributionSplitsConstants;
using SalaryConstants = PHSRAG.Insight21.Personnel.Constants.SalaryAdjustmentsConstants;
using ArgumentIndex = PHSRAG.Insight21.Personnel.PersonnelDetailsArgumentIndex;
#endregion

namespace PHSRAG.Insight21.Personnel
{
	#region DistributionPeriods class
	/// <summary>
	/// The DistributionPeriods class supports the loading of distribution period
	/// information for a given job map id and fiscal year id. This infomation is 
	/// generated on the fly using the distribution split and the salary adjustment data.
	/// It also provides indexing support to access the contained distribution period objects
	/// </summary>
	public class DistributionPeriods : BO.BusinessObject, IEnumerable
	{		
		#region Instance Variables
		private ArrayList distribPeriods;
		//Variables used in Distrib Period computation
		private System.DateTime startDate;
		private System.DateTime endDate;
		private decimal annualRate = 0;
		private decimal toBePaid = 0;
		private decimal percentEffort = 0;
		private decimal currentSalary = 0;
		private System.DateTime expirationDate;
		private System.DateTime effectiveDate;
		#endregion

		#region Public properties
		/// <summary>
		/// Get the count of DistributionPeriod objects contained
		/// </summary>
		public int Count
		{
			get{return distribPeriods.Count;}
		}
		#endregion

		#region Indexers
		/// <summary>
		/// Retrieve a DistributionPeriod from the DistributionPeriods instance, given 
		/// its ordinal position within the collection. If the ordinal position is 
		/// out of region, a null cast exception is thrown 
		/// </summary>
		public DistributionPeriod this[int index]
		{
			get{return (DistributionPeriod)distribPeriods[index];}
		}
		#endregion

		#region Constructor
		/// <summary>
		/// Intializes the base class with the cache policy and set the DataContainer of the present instance
		/// </summary>
		/// <param name="dataContainer">Dataset passed from the calling class</param>
		/// <param name="cachePolicy">The prevalent cache policy</param>
		public DistributionPeriods(DataSet dataContainer,ICachePolicy cachePolicy) : base (cachePolicy)
		{
			DataContainer = dataContainer;
		}
		#endregion

		#region IEnumerable Members
		/// <summary>
		/// Get the enumerator associated with the instance
		/// </summary>
		/// <returns>Object of type IEnumerator</returns>
		public IEnumerator GetEnumerator()
		{
			return distribPeriods.GetEnumerator();
		}
		#endregion
		
		#region BusinessObject methods
		/// <summary>
		/// This method is not used and only exists to satisfy interface requirements.
		/// </summary>
		public override void Cache()
		{
			throw new Exception("The distributionperiods object cannot be cached independently");
		}
		/// <summary>
		/// This method is not used and only exists to satisfy interface requirements.
		/// </summary>
		public override void UnCache()
		{
			throw new Exception("The distributionperiods object cannot be uncached independently");
		}
		/// <summary>
		/// Load the distribution period data accessible to the logged in user.
		/// <remarks>
		/// The load method does two things within its execution
		/// <list type="bullet">
		/// <item><description>Load the distribution splits data accessible to the logged in user and build the
		/// DistributionPeriods table on the fly using the splits and the salary adjustments data already loaded.</description></item>
		/// <item><description>Load the contained arraylist with as many DistributionPeriod objects as the number of
		/// rows in the DistributionPeriod table.</description></item>
		/// </list>
		/// </remarks> 
		/// </summary>
		/// <param name="connectionString">The database connection string</param>
		/// <param name="args">Is expected to contain 4 mandatory arguments -
		/// DomainUserID,EmployeeJobMapID,FiscalYearID and FiscalYear</param>
		public override void Load(string connectionString, params object[] args)
		{
			if (args.Length != 4)
				throw new Exception
					("Distribution periods load requires the domainuserid,a jobmapid,a fiscalyearid and the fiscal year");
			
			DataAccess.LoadDataSet
				(DataContainer
				,SplitConstants.DistributionSplitsTableName
				,connectionString
				,SplitConstants.GetDistributionSplitsSPROCName
				,Utility.DataAccess.BuildSqlParameterArray(
				"@domainUserId",SqlDbType.Int,args[(int)ArgumentIndex.DomainUserID]
				,"@employeeJobMapId", SqlDbType.Int, args[(int)ArgumentIndex.EmployeeJobMapID]
				,"@fiscalYearId",SqlDbType.Int,args[(int)ArgumentIndex.FiscalYearID]));

			int fiscalYear = Convert.ToInt32(args[(int)ArgumentIndex.FiscalYear]);

			//Subtract one from the fiscal year to get the previous year and get the start date.
			startDate = Convert.ToDateTime(string.Format("{0}{1}"
				,Constants.FiscalYearStartDate,(fiscalYear - 1).ToString()));

			//Get the end date
			endDate = Convert.ToDateTime(string.Format("{0}{1}"
				,Constants.FiscalYearEndDate,fiscalYear.ToString()));

			BuildDistributionPeriods();

			distribPeriods = new ArrayList();
			foreach(DataRow periodRow in 
				DataContainer.Tables[DistribPeriodConstants.DistributionPeriodsTableName].Rows)
			{
				distribPeriods.Add(new DistributionPeriod(periodRow));
			}
		}
		#endregion

		#region Private methods
		/// <summary>
		/// Builds the distribution periods using the splits and the salary adjustment data
		/// <remarks>
		/// The business logic used in the building of the distribution periods depends on the split data
		/// and the salary adjustment data.
		/// Following is the list of different scenarios and the resultant logic used
		/// <list type="bullet">
		/// <item><description>Get the splits within the active fiscalyear</description></item>
		/// <item><description>If there are splits available for the active fiscal year group the splits
		/// with the same effective and expiration dates into one distribution period,getting the current
		/// salary from the salary adjustment data.</description></item>
		/// <item><description>Check if there are any salary adjustments effective in the active fiscal year
		/// which have effective dates greater than the maximum effective date of the distribution periods calculated.
		/// If yes, build empty distribution periods with just effective date, expiration date and current
		/// salary and no splits</description></item>
		/// <item><description>If there no are splits available for the active fiscal year group get the salary adjustments
		/// which are effective in the active fiscal year.Build distributionperiods with
		/// no splits for each of the salaries</description></item>
		/// </list>
		/// </remarks> 
		/// </summary>
		private void BuildDistributionPeriods()
		{			
			//Select the splits which fall within the active fiscal year
			DataRow[] splitRows =
				DataContainer.Tables[SplitConstants.DistributionSplitsTableName].Select
				(string.Format("{0} >= '{1}' AND {0} <= '{2}'",
				DistribPeriodConstants.EffectiveDateColumnName,startDate,endDate));

			if(splitRows.Length > 0)
			{
				CreateandAddDistribPeriodTable();

				//Get the effective date and expiration from the first row in the row collection
				effectiveDate = (DateTime)splitRows[0][SplitConstants.EffectiveDateColumnName];
				expirationDate = (DateTime)splitRows[0][SplitConstants.ExpirationDateColumnName];

				foreach(DataRow splitRow in splitRows)
				{
					//Add the items untill the effective date changes
					if (effectiveDate == (DateTime)splitRow[SplitConstants.EffectiveDateColumnName])
					{
						annualRate += (decimal)splitRow[SplitConstants.AnnualRateColumnName];
						object toBePaidValue = splitRow[SplitConstants.ToBePaidColumnName];
						if(toBePaidValue != DBNull.Value)
                            toBePaid += (decimal)toBePaidValue;
						percentEffort += (decimal)splitRow[SplitConstants.PercentEffortColumnName];
						effectiveDate = (DateTime)splitRow[SplitConstants.EffectiveDateColumnName];
						expirationDate = (DateTime)splitRow[SplitConstants.ExpirationDateColumnName];
					}
						//If effective date changes,Add a distrib period row with the calculated data
						//and set the data for the next distrib period row
					else
					{
						AddDistribPeriodRow();

						//Reassign the values with values from the current row
						effectiveDate = (DateTime)splitRow[SplitConstants.EffectiveDateColumnName];
						expirationDate = (DateTime)splitRow[SplitConstants.ExpirationDateColumnName];
						annualRate = (decimal)splitRow[SplitConstants.AnnualRateColumnName];
						object toBePaidValue = splitRow[SplitConstants.ToBePaidColumnName];
						if(toBePaidValue != DBNull.Value)
							toBePaid = (decimal)toBePaidValue;
						percentEffort = (decimal)splitRow[SplitConstants.PercentEffortColumnName];
					}
			
					//Get the current salary for the period
					GetSalaryForPeriod(splitRow);
				}

				//Add the final set of data to the DistribPeriod table
				AddDistribPeriodRow();

				//Pick Salaries which are effective in this fiscalyear and with dates 
				//greater than the Max Epiration date picked from the distrib periods calculated
				DataRow[] salaryRows = DataContainer.Tables[SalaryConstants.SalaryAdjustmentsTableName].
					Select(string.Format("{0} >= '{1}' AND {0} <= '{2}'",
					DistribPeriodConstants.EffectiveDateColumnName
					,(DateTime)DataContainer.Tables[DistribPeriodConstants.DistributionPeriodsTableName].
					Select(string.Format("{0}=Max({0})",DistribPeriodConstants.ExpirationDateColumnName))[0]
					[DistribPeriodConstants.ExpirationDateColumnName]
					,endDate));

				if(salaryRows.Length > 0)
					BuildEmptyDistribPeriods(salaryRows);
			}
			else
			{
				CreateandAddDistribPeriodTable();
				
				//Select Salaries with Effective Date greater than the
				//current financial year start and less the current financial year end
				DataRow[] salaryRows = DataContainer.Tables[SalaryConstants.SalaryAdjustmentsTableName].
					Select(string.Format("{0} >= '{1}' AND {0} <= '{2}'",
					DistribPeriodConstants.EffectiveDateColumnName,startDate,endDate));

				if (salaryRows.Length > 0)
					BuildEmptyDistribPeriods(salaryRows);
			}
		}
		/// <summary>
		/// Adds the distribution period table and the appropraite columns to the data container
		/// </summary>
		private void CreateandAddDistribPeriodTable()
		{
			DataTable periodTable = new DataTable(DistribPeriodConstants.DistributionPeriodsTableName);

			periodTable.Columns.Add
				(new DataColumn(DistribPeriodConstants.EffectiveDateColumnName,typeof(System.DateTime)));
			periodTable.Columns.Add
				(new DataColumn(DistribPeriodConstants.ExpirationDateColumnName,typeof(System.DateTime)));
			periodTable.Columns.Add
				(new DataColumn(DistribPeriodConstants.TotalAnnualRateColumnName,typeof(System.Decimal)));
			periodTable.Columns.Add
				(new DataColumn(DistribPeriodConstants.TotalToBePaidColumnName,typeof(System.Decimal)));
			periodTable.Columns.Add
				(new DataColumn(DistribPeriodConstants.TotalPercentEffortColumnName,typeof(System.Decimal)));
			periodTable.Columns.Add
				(new DataColumn(DistribPeriodConstants.CurrentSalaryColumnName,typeof(System.Decimal)));
			
			DataContainer.Tables.Add(periodTable);
		}
		/// <summary>
		/// Add a row with the appropriate data to the distribution period table in the data container
		/// </summary>
		private void AddDistribPeriodRow()
		{
			DataContainer.Tables[DistribPeriodConstants.DistributionPeriodsTableName].Rows.Add
				(new object[]{effectiveDate,expirationDate,annualRate,toBePaid,percentEffort,currentSalary});
		}
		/// <summary>
		/// Set the value of the currentsalary for the period from the salary adjustment table
		/// </summary>
		/// <param name="splitRow">Datarow with split information</param>
		private void GetSalaryForPeriod(DataRow splitRow)
		{
			//The select is done on the ID column. So directly indexing to get the data
			currentSalary = (decimal)DataContainer.Tables[SalaryConstants.SalaryAdjustmentsTableName].
				Select(string.Format("{0}='{1}'"
				,SalaryConstants.SalaryIDColumnName
				,splitRow[SplitConstants.SalaryAdjustmentIDColumnName]))[0][SalaryConstants.SalaryColumnName];
		}
		/// <summary>
		/// Build a DistribPeriod row for which there is no corresponding Splits
		/// If there is only one salaryRow set the expiration date to the fiscal year end date
		/// If there are more then one salary row then the expiration date for a period
		/// should be the next salary adjustment effective date minus one day
		/// </summary>
		/// <param name="salaryRows">PersonnelDataset.SalaryRow Collection</param>
		private void BuildEmptyDistribPeriods(DataRow[] salaryRows)
		{
			if (salaryRows.Length == 1)
			{
				expirationDate = endDate; //endDate is fiscal year end date 
				AddEmptyDistribPeriodRow(salaryRows[0]);
			}
			else
			{
				int nextRowIndex = 1;
				
				foreach(DataRow salaryRow in salaryRows)
				{
					if (nextRowIndex >= salaryRows.Length)
					{
						//For the last row the expiration date will be fiscal year end date
						expirationDate = endDate;
						AddEmptyDistribPeriodRow(salaryRow);
					}
					else
					{
						//Subtract one day from the next row effective date
						expirationDate = ((DateTime)salaryRows[nextRowIndex]
							[SalaryConstants.EffectiveDateColumnName]).AddDays(-1);
						AddEmptyDistribPeriodRow(salaryRow);
					}
					nextRowIndex++;				
				}
			}			
		}
		/// <summary>
		/// Add a DistribRow with no Salary Distrib info and
		/// only CurrentSalary,Effective and Expiration dates
		/// </summary>
		/// <param name="salaryRow">Datarow with salary information</param>
		private void AddEmptyDistribPeriodRow(DataRow salaryRow)
		{
			DataTable distribPeriodsTable =
				DataContainer.Tables[DistribPeriodConstants.DistributionPeriodsTableName];
			
			DataRow distribPeriodRow = distribPeriodsTable.NewRow();

			distribPeriodRow[DistribPeriodConstants.EffectiveDateColumnName]= 
				salaryRow[SalaryConstants.EffectiveDateColumnName];
			distribPeriodRow[DistribPeriodConstants.ExpirationDateColumnName]= expirationDate;
			distribPeriodRow[DistribPeriodConstants.CurrentSalaryColumnName]= 
				salaryRow[SalaryConstants.SalaryColumnName];

			distribPeriodsTable.Rows.Add(distribPeriodRow);
		}
		#endregion
	}
	#endregion
}